#pragma once

#define MDTX_NO_ARMADILLO

#include "pybind11/pybind11.h"
#include "pybind11/numpy.h"

#include "mdtx-online/utility/mk_tuple.h"

namespace py = pybind11;

template <class T>
class unary1 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    value_type run(input_type x1)
    {
        return insert(x1);
    }

    array_out run2(array_in x1)
    {
        auto size = x1.size();
        array_out y1(size);

        auto rx1 = x1.unchecked();
        auto ry1 = y1.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            ry1(i) = insert(rx1(i));
        }

        return y1;
    }
};

template <class T>
class unary2 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type> run(input_type x1)
    {
        return mdtx::online::mk_tuple(insert(x1));
    }

    std::tuple<array_out, array_out> run2(array_in x1)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);

        auto rx1 = x1.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i)) = mdtx::online::mk_tuple(insert(rx1(i)));
        }

        return std::make_tuple(y1, y2);
    }
};

template <class T>
class unary3 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type, value_type> run(input_type x1)
    {
        return mdtx::online::mk_tuple(insert(x1));
    }

    std::tuple<array_out, array_out, array_out> run2(array_in x1)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);
        array_out y3(size);

        auto rx1 = x1.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        auto ry3 = y3.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i), ry3(i)) = mdtx::online::mk_tuple(insert(rx1(i)));
        }

        return std::make_tuple(y1, y2, y3);
    }
};

template <class T>
class unary4 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type, value_type, value_type> run(input_type x1)
    {
        return mdtx::online::mk_tuple(insert(x1));
    }

    std::tuple<array_out, array_out, array_out, array_out> run2(array_in x1)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);
        array_out y3(size);
        array_out y4(size);

        auto rx1 = x1.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        auto ry3 = y3.mutable_unchecked();
        auto ry4 = y4.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i), ry3(i), ry4(i)) = mdtx::online::mk_tuple(insert(rx1(i)));
        }

        return std::make_tuple(y1, y2, y3, y4);
    }
};

template <class T>
class binary1 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    value_type run(input_type x1, input_type x2)
    {
        return insert(x1, x2);
    }

    array_out run2(array_in x1, array_in x2)
    {
        auto size = x1.size();
        array_out y1(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto ry1 = y1.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            ry1(i) = insert(rx1(i), rx2(i));
        }

        return y1;
    }
};

template <class T>
class binary2 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type> run(input_type x1, input_type x2)
    {
        return mdtx::online::mk_tuple(insert(x1, x2));
    }

    std::tuple<array_out, array_out> run2(array_in x1, array_in x2)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i)) = mdtx::online::mk_tuple(insert(rx1(i), rx2(i)));
        }

        return std::make_tuple(y1, y2);
    }
};

template <class T>
class binary3 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type, value_type> run(input_type x1, input_type x2)
    {
        return mdtx::online::mk_tuple(insert(x1, x2));
    }

    std::tuple<array_out, array_out, array_out> run2(array_in x1, array_in x2)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);
        array_out y3(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        auto ry3 = y3.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i), ry3(i)) = mdtx::online::mk_tuple(insert(rx1(i), rx2(i)));
        }

        return std::make_tuple(y1, y2, y3);
    }
};

template <class T>
class binary4 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    std::tuple<value_type, value_type, value_type, value_type> run(input_type x1, input_type x2)
    {
        return mdtx::online::mk_tuple(insert(x1, x2));
    }

    std::tuple<array_out, array_out, array_out, array_out> run2(array_in x1, array_in x2)
    {
        auto size = x1.size();
        array_out y1(size);
        array_out y2(size);
        array_out y3(size);
        array_out y4(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto ry1 = y1.mutable_unchecked();
        auto ry2 = y2.mutable_unchecked();
        auto ry3 = y3.mutable_unchecked();
        auto ry4 = y4.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            std::tie(ry1(i), ry2(i), ry3(i), ry4(i)) = mdtx::online::mk_tuple(insert(rx1(i), rx2(i)));
        }

        return std::make_tuple(y1, y2, y3, y4);
    }
};

template <class T>
class ternary1 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    value_type run(input_type x1, input_type x2, input_type x3)
    {
        return insert(x1, x2, x3);
    }

    array_out run2(array_in x1, array_in x2, array_in x3)
    {
        auto size = x1.size();
        array_out y1(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto rx3 = x3.unchecked();
        auto ry1 = y1.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            ry1(i) = insert(rx1(i), rx2(i), rx3(i));
        }

        return y1;
    }
};

template <class T>
class quadary1 : public T
{
protected:
    using T::insert;

public:
    using T::T;

    using typename T::input_type;
    using array_in = py::array_t<input_type>;
    using typename T::value_type;
    using array_out = py::array_t<value_type>;

    value_type run(input_type x1, input_type x2, input_type x3, input_type x4)
    {
        return insert(x1, x2, x3, x4);
    }

    array_out run2(array_in x1, array_in x2, array_in x3, array_in x4)
    {
        auto size = x1.size();
        array_out y1(size);

        auto rx1 = x1.unchecked();
        auto rx2 = x2.unchecked();
        auto rx3 = x3.unchecked();
        auto rx4 = x4.unchecked();
        auto ry1 = y1.mutable_unchecked();
        for (py::ssize_t i = 0; i < size; ++i)
        {
            ry1(i) = insert(rx1(i), rx2(i), rx3(i), rx4(i));
        }

        return y1;
    }
};
